package com.demo.gulimall.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.demo.gulimall.common.to.mq.SeckillOrderTo;
import com.demo.gulimall.common.utils.R;
import com.demo.gulimall.seckill.entity.SeckillSession;
import com.demo.gulimall.seckill.entity.SeckillSkuRelation;
import com.demo.gulimall.seckill.feign.ProductFeignService;
import com.demo.gulimall.seckill.service.SeckillService;
import com.demo.gulimall.seckill.service.SeckillSessionService;
import com.demo.gulimall.seckill.to.SecKillSkuRedisTo;
import com.demo.gulimall.seckill.vo.SkuInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Chen Yuan
 * @create 2023-08-07  15:17
 */
@Service
@Slf4j
public class SeckillServiceImpl implements SeckillService {
	private final static String SESSION_CACHE_PRE = "seckill:session:";
	private final static String SKU_CACHE_PRE = "seckill:sku:";
	private final static String SKU_STOCK_SEMAPHORE_PRE = "seckill:stock:";

	@Autowired
	private StringRedisTemplate redisTemplate;

	@Autowired
	private RabbitTemplate rabbitTemplate;

	@Autowired
	private RedissonClient redissonClient;

	@Autowired
	private SeckillSessionService seckillSessionService;

	@Autowired
	private ProductFeignService productFeignService;

	@Override
	public void uploadSeckillSkuLatestThreeDays() {
		// 1. 扫描近三天需要参与秒杀的活动
		List<SeckillSession> sessionList = seckillSessionService.getLatestThreeDaySession();

		// 2. 将商品数据缓存进 Redis
		this.saveSessionInfos(sessionList);
		this.saveSessionSkuInfos(sessionList);


	}

	@Override
	public String kill(String killId, String code, Integer num, Long userId) {
	//1. 获取当前秒杀商品的详细信息
		BoundHashOperations<String, String, String> skuOps = redisTemplate.boundHashOps(SKU_CACHE_PRE);
		String skuInfo = skuOps.get(killId);
		if (skuInfo == null) {
			// 没有当前秒杀商品信息
			return null;
		}
		SecKillSkuRedisTo skuRedisTo = JSON.parseObject(skuInfo, SecKillSkuRedisTo.class);

	//2. 校验数据合法性
		// 校验时间
		Long startTime = skuRedisTo.getStartTime();
		Long endTime = skuRedisTo.getEndTime();
		Long nowTime = new Date().getTime();
		if (nowTime < startTime || nowTime > endTime) {
			// 不在秒杀时间范围内
			return null;
		}
		// 校验随机码
		if (!Objects.equals(skuRedisTo.getRandomCode(), code)) {
			return null;
		}
		// 校验商品数量
		if (num > skuRedisTo.getSeckillLimit()) {
			return null;
		}
		// 当前用户是否已购买过
		String userPayedKey = userId + "_" + skuRedisTo.getPromotionSessionId() + "_" + skuRedisTo.getSkuId();
		Boolean absent = redisTemplate.opsForValue().setIfAbsent(userPayedKey, num.toString(), endTime - nowTime, TimeUnit.MILLISECONDS);
		if (!absent) {
			// 占位失败, 则该用户之前买过
			return null;
		}

	// 3. 判断信号量
		RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE_PRE + skuRedisTo.getRandomCode());
		// 尝试获取信号量
		boolean acquire = false;
		try {
			acquire = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
		} catch (InterruptedException e) {
			return null;
		}

		if (!acquire) {
			return null;
		}

	// 4. 抢购成功, 发送 MQ 下订单
		SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
		String orderSn = IdWorker.getIdStr();
		seckillOrderTo.setOrderSn(orderSn)
				.setMemberId(userId)
				.setSeckillNum(num)
				.setSkuId(skuRedisTo.getSkuId())
				.setSeckillPrice(skuRedisTo.getSeckillPrice())
				.setPromotionSessionId(skuRedisTo.getPromotionSessionId());

		// 在 Order 服务中创建队列, 并监听
		rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", seckillOrderTo);
		return orderSn;
	}


	// 保存活动信息
	private void saveSessionInfos(List<SeckillSession> sessionList){
		sessionList.forEach(session -> {
			long startTime = session.getStartTime().getTime();
			long endTime = session.getEndTime().getTime();
			String redisKey = SESSION_CACHE_PRE + startTime + "_" + endTime;

			// 如果活动已经上架, 则跳过
			if (!redisTemplate.hasKey(redisKey)) {
				List<String> skuIds = session.getRelations().stream()
						.map(relation -> relation.getPromotionSessionId() + "_" + String.valueOf(relation.getSkuId()))
						.collect(Collectors.toList());
				redisTemplate.opsForList().leftPushAll(redisKey, skuIds);
			}
		});
	}

	// 保存活动涉及到的商品信息
	private void saveSessionSkuInfos(List<SeckillSession> sessionList){
		sessionList.forEach(session -> {
			BoundHashOperations<String, String, String> skuOps = redisTemplate.boundHashOps(SKU_CACHE_PRE);

			session.getRelations().forEach(relation -> {
				String skuRedisKey = relation.getPromotionSessionId() + "_" + relation.getSkuId();

				if (skuOps.hasKey(skuRedisKey)){
					// 如果存在, 则跳过
					return;
				}

				SecKillSkuRedisTo skuRedisTo = new SecKillSkuRedisTo();
				// sku 的基本信息
				R<SkuInfoVo> skuInfoRes = productFeignService.skuInfo(relation.getSkuId());
				if (skuInfoRes.isSuccess()) {
					skuRedisTo.setSkuInfoVo(skuInfoRes.getData());
				}

				// sku 的秒杀信息
				BeanUtils.copyProperties(relation, skuRedisTo);

				// 设置当前商品秒杀时间信息
				skuRedisTo.setStartTime(session.getStartTime().getTime());
				skuRedisTo.setEndTime(session.getEndTime().getTime());

				// 随机码, 只有在秒杀开始时才暴露, 方式拼接请求攻击秒杀链接
				String randomCode = UUID.randomUUID().toString().replace("-", "");
				skuRedisTo.setRandomCode(randomCode);

				skuOps.put(skuRedisKey, JSON.toJSONString(skuRedisTo));

				// 使用 Redission 添加信号量, 信号量与库存对应, 大量请求访问时先减信号量, 若信号量小于等于0, 则直接返回抢购失败
				RSemaphore stockSem = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE_PRE + randomCode);
				stockSem.trySetPermits(relation.getSeckillCount());
			});

		});
	}
}
